from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from threading import Thread
from queue import Queue
import os, sys, time
import requests
import json, re
import logging
import requests
import cv2 as cv
import base64
import numpy as np
from selenium.webdriver import ActionChains
import pymysql
import traceback


class DB(object):
    def __init__(self):
        self.conn = pymysql.connect(host='pdd-master.local',
            user='pdduser',
            password='giEWcbTQ3bueAiw1',
            database='pinduoduo',
            port=3306,
            charset='utf8mb4')
        self.cursor = self.conn.cursor()

    def execute(self, sql, param):
        insertid = 0
        try:
            self.cursor.execute(sql, param)
            if sql.startswith('insert'):
                insertid = self.cursor.lastrowid
            self.conn.commit()
        except:
            pass
        return insertid

    def fetchall(self, sql, param):
        try:
            self.cursor.execute(sql, param)
            res = self.cursor.fetchall()
        except:
            res = None
        return res

    def fetchone(self, sql):
        pass
        #[0][0]

    def close(self):
        try:
            self.cursor.close()
            self.conn.close()
        except:
            pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        try:
            self.conn.close()
        except:
            pass

class Pdddriver(object):
    # 2020.11.16 liweimin
    ddqueue  = Queue()
    cookies  = {}
    boot_url = ''
    proxy_server = ''
    anti_jsx = ''


    def __init__(self, id):
        self.__initlogger(id)
        self.time2wait = 15
        self.boot_url = 'https://mms.pinduoduo.com/login'
        self.proxy_server = '127.0.0.1:8080'
        ex_path = 'C:\Program Files\Google\Chrome\Application\chromedriver.exe'
        chrome_options = Options()
        chrome_options.add_experimental_option('excludeSwitches', ['enable-automation'])
        # chrome_options.add_argument("--proxy-server=http://%s" % self.proxy_server)
        agent = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36'
        chrome_options.add_argument('user-agent="%s"' % agent)
        self.browser = webdriver.Chrome(executable_path=ex_path, chrome_options=chrome_options)
        self.browser.delete_all_cookies()
        # self.browser.set_window_size(800,600)
        self.browser.implicitly_wait(self.time2wait)
        self.browser.get(self.boot_url)
        self.wait = WebDriverWait(self.browser, self.time2wait)

    def __initlogger(self, id):
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(logging.DEBUG)
        fh = logging.FileHandler(
            'logs/pdd_{}_{}.log'.format(id, time.strftime('%Y_%m_%d', time.localtime(time.time()))), encoding='utf-8',
            mode='a')
        formatter = logging.Formatter("%(asctime)s - %(message)s", datefmt='%Y-%m-%d %H:%M:%S')
        fh.setFormatter(formatter)
        self.logger.addHandler(fh)
        sh = logging.StreamHandler()
        sh.setFormatter(formatter)
        self.logger.addHandler(sh)

    def quit(self):
        try:
            self.logger.handlers.pop()
        except Exception as e:
            pass
        self.browser.quit()

    def __del__(self):
        self.logger.handlers.pop()
        self.browser.quit()

    def __inputUserinfo(self, username, password):
        try:
            _item  = self.wait.until(EC.element_to_be_clickable((By.XPATH, '//*[@id="root"]/div/div/div/main/div/section[2]/div/div/div/div[1]/div/div[2]')))
            _item.click()
            time.sleep(1)
            _input = self.wait.until(EC.presence_of_element_located((By.ID, "usernameId")))
            _input.send_keys(username)
            time.sleep(1)
            _input = self.wait.until(EC.presence_of_element_located((By.ID, "passwordId")))
            _input.send_keys(password)
            time.sleep(1)
        except Exception as e:
            self.logger.info('Error---%s' % str(e))
            self.logger.info('--------%s' % traceback.format_exc())

    def __clickLoginbtn(self):
        _btn  = self.wait.until(EC.element_to_be_clickable((By.XPATH, '//*[@id="root"]/div/div/div/main/div/section[2]/div/div/div/div[2]/section/div/div[2]/button')))
        _btn.click()
        time.sleep(self.time2wait)
        try:
            mask = self.wait.until(EC.element_to_be_clickable((By.ID, 'mms-header__mask')))
            mask.click()
            #EC.text_to_be_present_in_element((By.CLASS_NAME, 'nav-item-text'), r'后台首页')
            _link = self.wait.until(EC.element_to_be_clickable((By.XPATH, '//*[@id="__next"]/div/div/nav/div/div/nav/div/div[3]/ul/li[1]/a')))
            _link.click()
            return True
        except Exception as e:
            self.logger.info('Error---%s' % str(e))
            self.logger.info('--------%s' % traceback.format_exc())
            return False

    def __readCookies(self, return_text = False):
        _cookies = ''
        if return_text is True:
            for k, v in self.cookies.items():
                _cookies += '%s=%s; ' % (k, v)
            return _cookies

        self.cookies = {}
        for ck in self.browser.get_cookies():
            self.cookies[ck['name']] = ck['value']
        self.cookies['x-visit-time'] = int(round(time.time() * 1000)) - 3000

    def anti_content(self):
        '''
        _url = "http://127.0.0.1:8000/get_anti_content"
        _response = requests.get(_url)
        anticontent = json.loads(_response.text)['anti_result']
        return anticontent
        '''
        if self.anti_jsx is '':
            with open('anti.js', 'r', encoding='utf-8') as f:
                self.anti_jsx = f.read()
        try:
            self.browser.execute_script(self.anti_jsx)
            time.sleep(1)
            anti_content = self.browser.execute_script('return window.run_anti_content||"";')
        except Exception as e:
            self.logger.info('anti_error: %s' % str(e))
            anti_content = ''
        return anti_content

    def localtime(self, dateAndTime = False):
        if dateAndTime is True:
            return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
        return time.strftime('%Y-%m-%d', time.localtime(time.time()))


    def submitLogin(self, username, password):
        self.logger.info('准备登录')
        self.__inputUserinfo(username, password)
        if self.__clickLoginbtn():
            self.__readCookies()
            self.logger.info('登录成功')
            self.get_OrderList()
        else:
            self.logger.info('登录失败')
            self.quit()


    def get_OrderList(self):
        # 子线程处理订单保存工作
        t = Thread(target=self.saveOrder)
        t.start()
        # 主线程循环读取订单信息
        while True:
            self.recentOrderList()
            time.sleep(120)

    def recentOrderList(self, pageNumber = 1):
        url  = "https://mms.pinduoduo.com/mangkhut/mms/recentOrderList"
        headers = {
            "Host": "mms.pinduoduo.com",
            "User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
            "Accept": "application/json",
            "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
            "Accept-Encoding": "gzip, deflate, br",
            "Referer": "https://mms.pinduoduo.com/orders/list",
            "Content-Type": "application/json;charset=UTF-8",
            "Anti-Content": self.anti_content(),
            "Origin": "https://mms.pinduoduo.com",
            "Cookie": self.__readCookies(True)
        }
        ed_time = int(time.time())
        st_time = ed_time - 86400*7  #7776000  #90天
        data = {
            "orderType":1,         #0 全部  1 待发货
            "afterSaleType":1,
            "remarkStatus":-1,
            "urgeShippingStatus":-1,
            "groupStartTime":st_time,
            "groupEndTime":ed_time,
            "pageNumber":pageNumber,
            "pageSize":20,
            "sortType":7
        }
        response = requests.post(url, headers=headers, data=json.dumps(data))
        # print(response.text)
        res = json.loads(response.text)
        if res['errorCode'] is 0:
            if res['result']['totalItemNum'] > 0:
                for item in res['result']['pageItems']:
                    self.logger.info('查看订单列表---%s' % item['order_sn'])
                    _path      = 'data/%s/%s.txt' % (self.localtime(), item['order_sn'])
                    _path_mob  = 'data/%s/%s-mobile.txt' % (self.localtime(), item['order_sn'])
                    _path_addr = 'data/%s/%s-address.txt' % (self.localtime(), item['order_sn'])
                    if not os.path.isdir(_path[:15]):
                        os.makedirs(_path[:15])
                    if not os.path.exists(_path):
                        self.newOrderDetail_js(item['order_sn'], _path)
                        self.newOrderPage(item['order_sn'], _path_mob)
                        self.ddqueue.put(item['order_sn'])
                        #self.receiverMobile_js(item['order_sn'], _path_mob)
                        #self.receiverAddress_js(item['order_sn'], _path_addr)

                totalPage = int((int(res['result']['totalItemNum']) + 20 - 1) / 20)
                if pageNumber < totalPage:
                    self.recentOrderList(pageNumber+1)

    def saveOrder(self):
        while True:
            ordersn = self.ddqueue.get()
            if ordersn is not None:
                # 保存到数据库
                if self.file2Mysql(ordersn):
                    self.ddqueue.task_done()
                else:
                    self.ddqueue.put(ordersn)

            time.sleep(1)

    def file2Mysql(self, orderSn=''):
        try:
            _path = 'data/%s/%s.txt' % (self.localtime(), orderSn)
            _path_mob = 'data/%s/%s-mobile.txt' % (self.localtime(), orderSn)
            if os.path.exists(_path) and os.path.exists(_path_mob):
                _order, _order_mob, _spec, _name, _phone, _address, _city = None, None, '', '', '', '', ''
                with open(_path, 'r', encoding='utf-8') as f:
                    _order = json.load(f)
                    if _order['success'] is True:
                        _spec  = _order['result']['spec']
                with open(_path_mob, 'r', encoding='utf-8') as f:
                    _order_mob = json.load(f)
                    if _order_mob['success'] is True:
                        _name    = _order_mob['result']['receiver']
                        _phone   = _order_mob['result']['mobile']
                        _address = _order_mob['result']['address']
                        _city    = self.__resolveCity(_address)
                if _order is not None and _order_mob is not None:
                    sql = "insert into delivery_form(" \
                          "order_id, order_no, dot_id, user_id, send_name, send_phone, send_area, send_address, kg, " \
                          "receive_name, receive_phone, original_address, receive_area, receive_address, create_time, goods, order_third) values(" \
                          "'', '', 0, 0, '翟', '157****2995', '山西 太原 小店区', '云仓', 3, " \
                          "%s, %s, %s, %s, %s, %s, %s, %s)"
                    param = (_name, _phone, _address, _city, _address, self.localtime(True), _spec, orderSn)
                    db = DB()
                    insertid = db.execute(sql, param)
                    order_id = str(insertid).rjust(13, '0')
                    sql = "update delivery_form set order_id=%s where id=%s"
                    param = (str(order_id), insertid)
                    db.execute(sql, param)
                    db.close()
                self.logger.info('保存订单信息---%s' % orderSn)
                return True
            else:
                return False
        except Exception as e:
            ef = e.__traceback__.tb_frame.f_globals["__file__"]
            ln = e.__traceback__.tb_lineno
            self.logger.info('保存订单信息失败---%s---%s[%s,%s]' % (orderSn, str(e), ef, ln))
            return False

    def __resolveCity(self, address = ''):
        _ak_ = '3WBjOlcPjGVWCbfUl7yz7losYeb'
        city = ''
        tmp = requests.get('http://api.map.baidu.com/geocoding/v3/?address=%s&output=json&ak=%s' % (address, _ak_))
        res = json.loads(tmp.text)
        if res['status'] == 0:
            lng, lat = res['result']['location']['lng'], res['result']['location']['lat']
            tmp = requests.get('http://api.map.baidu.com/reverse_geocoding/v3/?ak=%s&output=json&location=%s,%s' % (_ak_, lat, lng))
            res = json.loads(tmp.text)
            if res['status'] == 0:
                fmrt = res['result']['addressComponent']
                city = '%s %s %s' % (fmrt['province'], fmrt['city'], fmrt['district'])
        return city


    def __slide_drag(self):
        # 获取 背景图与滑动块图片的连接
        background_image_url = self.browser.find_element_by_class_name('slider-img-bg').get_attribute('src')
        slider_image_url = self.browser.find_element_by_class_name('slider-item').get_attribute('src')

        # 获取两张图片 并保存
        background_image = background_image_url.replace('data:image/jpeg;base64,','')
        slider_image = requests.replace('data:image/png;base64,','')

        background_image_path = 'background_image' + '.jpg'
        with open(background_image_path, mode='wb') as f:
            f.write(base64.b64decode(background_image))
        slider_image_path = 'slider_image' + '.jpg'
        with open(slider_image_path, mode='wb') as f:
            f.write(base64.b64decode(slider_image))
        sleep(1)
        # 读取图片
        background = cv.imread(background_image_path)
        slider = cv.imread(slider_image_path)

        # 灰度处理图片 图片只有白黑
        background = cv.cvtColor(background, cv.COLOR_BGR2GRAY)
        slider = cv.cvtColor(slider, cv.COLOR_BGR2GRAY)
        # 去除白色部分 获取滑块正常大小
        slider = slider[slider.any(1)]

        # 进行相似度匹配返回坐标数组
        result = cv.matchTemplate(background, slider, cv.TM_CCOEFF_NORMED)

        # 获取相似度最高像素坐标 x 是像素高度 y 是像素距离
        x, y = np.unravel_index(result.argmax(), result.shape)

        # 等比例缩放问题 (滑动距离) 300比320 但是起始位置与背景图位置有偏差,不做等比例可以通过识别
        # distance = int(y*0.9375) - 2 # 等比例缩放 0.9375

        time.sleep(1)
        # 使用动作链
        btn = self.browser.find_element_by_class_name('slide-button')
        ActionChains(self.browser).drag_and_drop_by_offset(btn, xoffset=y, yoffset=0).perform()
        time.sleep(3)


    def newOrderPage(self, orderSn = '', filePath = ''):
        url = "https://mms.pinduoduo.com/orders/detail?type=4399&sn=%s" % orderSn
        self.browser.execute_script("window.open('%s')" % url)
        time.sleep(3)
        hds = self.browser.window_handles
        self.browser.switch_to.window(hds[1])
        time.sleep(1)
        try:
            EC.text_to_be_present_in_element((By.CSS_SELECTOR, r'slide-bg'), r'向右移动滑块')
            self.__slide_drag()
        except Exception as e:
            pass
        try:
            EC.text_to_be_present_in_element((By.LINK_TEXT, r'查看手机号'), r'查看手机号')
            _link_mob = self.wait.until(EC.element_to_be_clickable((By.LINK_TEXT, r'查看手机号')))
            _link_mob.click()
            time.sleep(2)
            EC.text_to_be_present_in_element((By.LINK_TEXT, r'查看姓名和地址'), r'查看姓名和地址')
            _link_addr = self.wait.until(EC.element_to_be_clickable((By.LINK_TEXT, r'查看姓名和地址')))
            _link_addr.click()
            time.sleep(2)
            _mob_path = '//*[@id="mf-mms-orders-container"]/div/div/div/div[5]/div[2]/div[1]/div[1]/div[2]/div'
            _mob_ = self.browser.find_element_by_xpath(_mob_path)
            _addr_path = '//*[@id="mf-mms-orders-container"]/div/div/div/div[5]/div[2]/div[1]/div[2]/div[2]/div'
            _addr_ = self.browser.find_element_by_xpath(_addr_path)
            with open(filePath, 'w', encoding='utf-8') as f:
                receiver, mob = str(_mob_.text).split(' ')
                data = {"success":True,"error_code":0,"result":{"order_sn":orderSn, "mobile": mob.strip(), "receiver": receiver.strip(), "address":_addr_.text}}
                f.write(json.dumps(data))
                self.logger.info('获取快递信息---%s' % orderSn)
        except Exception as e:
            self.logger.info('获取快递信息失败---%s---%s' % (orderSn, str(e)))
        time.sleep(1)

        self.browser.close()
        self.browser.switch_to.window(hds[0])

    def newOrderDetail(self, orderSn = '', filePath = ''):
        time.sleep(2)
        url = "https://mms.pinduoduo.com/mars/shop/newOrderDetail"
        headers = {
            "host": "mms.pinduoduo.com",
            "accept": "application/json",
            "accept-encoding": "gzip, deflate, br",
            "accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
            "anti-content": self.anti_content(),
            "content-length": "51",
            "content-type": "application/json;charset=UTF-8",
            "cookie": self.__readCookies(True),
            "origin": "https://mms.pinduoduo.com",
            "referer": "https://mms.pinduoduo.com/orders/list",
            "sec-fetch-dest": "empty",
            "sec-fetch-mode": "cors",
            "sec-fetch-site": "same-origin",
            "user-agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
        }
        data = {"orderSn": orderSn, "source": "MMS"}
        response = requests.post(url, headers=headers, data=json.dumps(data))
        with open(filePath, 'w', encoding='utf-8') as f:
            f.write(response.text)

    def newOrderDetail_js(self, orderSn = '', filePath = ''):
        try:
            anti_content = self.anti_content()
            time.sleep(1)
            script = 'fetch("https://mms.pinduoduo.com/mars/shop/newOrderDetail", ' \
                     '{method: "POST",' \
                     'body: JSON.stringify({"orderSn":"%s","source":"MMS"}),' \
                     'headers: {"Content-Type": "application/json","anti-content":"%s"},' \
                     'credentials: "include"}' \
                     ').then(function(response){' \
                     'response.json().then(function(res){window.order_json=res})});' % (orderSn, anti_content)
            self.browser.execute_script(script)
            time.sleep(1)
            order  = self.browser.execute_script('return JSON.stringify(window.order_json||{});')
            with open(filePath, 'w', encoding='utf-8') as f:
                f.write(order)
                self.logger.info('获取订单详情---%s' % orderSn)
        except Exception as e:
            self.logger.info('获取订单详情失败---%s---%s' % (orderSn, str(e)))


    def receiverMobile_js(self, orderSn = '', filePath = ''):
        try:
            anti_content = self.anti_content()
            time.sleep(1)
            script = 'fetch("https://mms.pinduoduo.com/fopen/order/receiver", ' \
                     '{method: "POST",' \
                     'body: JSON.stringify({"scene_code":"order_detail_mobile","receiver_info":["mobile"],"order_sn":"%s","biz_code":"order_detail"}),' \
                     'headers: {"Content-Type": "application/json","anti-content":"%s"},' \
                     'credentials: "include"}' \
                     ').then(function(response){' \
                     'response.json().then(function(res){window.order_mobile=res})});' % (orderSn, anti_content)
            self.browser.execute_script(script)
            time.sleep(1)
            order = self.browser.execute_script('return JSON.stringify(window.order_mobile||{});')
            with open(filePath, 'w', encoding='utf-8') as f:
                f.write(order)
                self.logger.info('获取联系人手机号成功---%s' % orderSn)
        except Exception as e:
            self.logger.info('获取联系人手机号失败---%s---%s' % (orderSn, str(e)))

    def receiverAddress_js(self, orderSn = '', filePath = ''):
        try:
            anti_content = self.anti_content()
            time.sleep(1)
            script = 'fetch("https://mms.pinduoduo.com/fopen/order/receiver", ' \
                     '{method: "POST",' \
                     'body: JSON.stringify({"scene_code":"order_detail_popup","receiver_info":["name","address"],"order_sn":"%s","biz_code":"order_detail"}),' \
                     'headers: {"Content-Type": "application/json","anti-content":"%s"},' \
                     'credentials: "include"}' \
                     ').then(function(response){' \
                     'response.json().then(function(res){window.order_address=res})});' % (orderSn, anti_content)
            self.browser.execute_script(script)
            time.sleep(1)
            order = self.browser.execute_script('return JSON.stringify(window.order_address||{});')
            with open(filePath, 'w', encoding='utf-8') as f:
                f.write(order)
                self.logger.info('获取联系人姓名地址成功---%s' % orderSn)
        except Exception as e:
            self.logger.info('获取联系人姓名地址失败---%s---%s' % (orderSn, str(e)))


if __name__ == '__main__':
    drv = Pdddriver(2)
    drv.submitLogin('liweimin', '111111')